﻿using System;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;

namespace O1.Kernel.Primitives
{
    [StructLayout(LayoutKind.Explicit)]
    internal struct Edge 
    {
        public const int Size = (Sz.CLR.Int32 * 3) + (Sz.CLR.Int16 * 2);

        public const int MaxEdgeType = EdgeTypeMask - 1;

        [FieldOffset(0)]
        public readonly uint EdgeType;

        [FieldOffset(Sz.CLR.Int32)]
        public readonly uint PropsCellId;

        [FieldOffset(Sz.CLR.Int32 + Sz.CLR.Int32)]
        public readonly uint EndVertexId;

        [FieldOffset(Sz.CLR.Int32 + Sz.CLR.Int32 + Sz.CLR.Int32)]
        public readonly ushort EndTrunkId;

        [FieldOffset(Sz.CLR.Int32 + Sz.CLR.Int32 + Sz.CLR.Int32 + Sz.CLR.Int16)]
        public readonly ushort PropsTrunkId;

        private const int EdgeTypeMask = 0x3FFFFFFF;

        private const int EdgeDirectionsMask = ~EdgeTypeMask;

        private const int EdgeDirectionsShift = 30;

        public Edge(uint edgeType, CellAddress endVertex, CellAddress props, EdgeDirections directions)
            : this()
        {
            this.EdgeType = EmbedDirections(edgeType, directions);
            this.EndVertexId = endVertex.CellId;
            this.EndTrunkId = endVertex.TrunkId;
            this.PropsCellId = props.CellId;
            this.PropsTrunkId = props.TrunkId;
        }

        public EdgeDirections Directions
        {
            get
            {
                return (EdgeDirections)((this.EdgeType & EdgeDirectionsMask) >> EdgeDirectionsShift);
            }
        }

        public int EdgeKey
        {
            get
            {
                return (int)(this.EdgeType & EdgeTypeMask);
            }
        }

        internal static unsafe Edge Read(byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Size < buffer.Length, ExceptionText.ValueBufferTooSmall);

            fixed (byte* p = &buffer[offset])
            {
                return *(Edge*)p;
            }
        }

        internal unsafe void Write(byte[] buffer, int offset)
        {
            Contract.Requires(buffer != null);
            Contract.Requires(0 <= offset);
            Contract.Requires(offset + Size < buffer.Length, ExceptionText.ValueBufferTooSmall);

            fixed (byte* p = &buffer[offset])
            {
                *((Edge*)p) = this;
            }
        }

        private static uint EmbedDirections(uint edgeType, EdgeDirections directions)
        {
            return (((uint)directions) << EdgeDirectionsShift) | (edgeType & EdgeTypeMask);
        }
    }
}
